package utils

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"crypto/md5"
	"errors"
)

type result struct {
	path string
	sum [md5.Size]byte
	err error
}

// 为什么返回error channel，而不是error
func sumFiles(done <-chan struct{}, root string) (<-chan result, <- chan error) {
	c := make(chan result)
	errc := make(chan error ,1)
	go func ()  {
		var wg sync.WaitGroup
		// 如果不开启外层 的goroutine，则Walk可能会卡住sumFiles
		err := filepath.Walk(root, func (path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if !info.Mode().IsRegular() {
				return nil
			}
			// 在准备开启一个goroutine前，发起Add
			wg.Add(1)
			go func ()  {
				data, err := ioutil.ReadFile(path)
				/// 为什么不要这里直接检验err，而要传到channel，供上层 inspect ?
				//这样做的好处是上层可以进行汇聚统计 ; 且goroutine没返回值，哪怕检测到err!=nil，也仅仅是打个log，远不够
				select {
				case c <- result{path, md5.Sum(data), err}:
					// 如果收到done,此协程有机会退出
				case <- done:
				}
				wg.Done()
			}()

			// abort the walk if done is closed
			select {
				// select的好处在于如果某事件没有发生，它不会卡着
			case <- done:
				return errors.New("walk cancelled")
			default:
				return nil
			}
		})

		// Walk has returned

		go func() {
			wg.Wait()
			// 所有文件都计算完了，可以关闭
			close(c)
		}()

		// 在这里 errc 也不必非得是buffered channel吧？？
		errc <- err
	}()

	return c, errc
}

func Tmd5AllParallel(root string) (map[string][md5.Size]byte, error) {
	done := make(chan struct{})
	defer close(done)

	c, errc := sumFiles(done, root)

	m := make(map[string][md5.Size]byte)
	for r := range c {
		// c里装了result的集合
		//// 在这里判断r.err，是为了得知每个处理file的协程是否出现了错误
		if r.err != nil {
			return nil, r.err
		}
		m[r.path] = r.sum
	}
	
	/// 为何不把这个放在 range c 前面？？
	if err := <- errc; err != nil {
		return nil, err
	}

	return m, nil
}